import { CommandBuilder } from '../../classes/command';
import { Embed } from '../../classes/embed';
import {
  getGroups,
  getUserEntry,
  getUserPermissions,
  getUserPermissionsFromEntry,
} from '../../util/db/auth';
import PermissionGroupModel from '../../util/db/models/permGroup';
import { UserPermission, defaultUserPermissions } from '../../util/db/perms';

const check = '✅',
  cross = '❎';

export const PermCommand = new CommandBuilder(
  'perm',
  'Perform a permission operation',
)
  .setRequiredPermissions('8')
  .addBuilderHook(builder =>
    builder
      .addSubcommandGroup(group =>
        group
          .setName('user')
          .setDescription('Perform an action on a user')
          .addSubcommand(subcommand =>
            subcommand
              .setName('set')
              .setDescription("Modify a user's permissions")
              .addUserOption(msg =>
                msg
                  .setName('user')
                  .setDescription('User to lookup')
                  .setRequired(true),
              )
              .addStringOption(msg =>
                msg
                  .setName('permission')
                  .setRequired(true)
                  .setDescription(
                    'Which permission should we modify on the user',
                  )
                  .setChoices(
                    ...Object.keys(defaultUserPermissions).map(v => ({
                      name: v,
                      value: v,
                    })),
                  ),
              )
              .addStringOption(msg =>
                msg
                  .setName('value')
                  .setRequired(true)
                  .setDescription('Which value this permission should have')
                  .setChoices(
                    {
                      name: 'true',
                      value: 'true',
                    },
                    {
                      name: 'false',
                      value: 'false',
                    },
                  ),
              ),
          )
          .addSubcommand(subcommand =>
            subcommand
              .setName('get')
              .setDescription("Get a user's permissions")
              .addUserOption(msg =>
                msg
                  .setName('user')
                  .setDescription('User to lookup')
                  .setRequired(true),
              )
              .addBooleanOption(msg =>
                msg
                  .setName('forcecreate')
                  .setDescription(`Create the entry if it doesn\'t exist`)
                  .setRequired(false),
              ),
          )
          .addSubcommand(subcommand =>
            subcommand
              .setName('addgroup')
              .setDescription('Adds a user to a group')
              .addStringOption(msg =>
                msg
                  .setName('group')
                  .setDescription(`Group to add to`)
                  .setRequired(true),
              )
              .addUserOption(msg =>
                msg
                  .setName('user')
                  .setDescription('User to modify - defaults to self')
                  .setRequired(false),
              ),
          )
          .addSubcommand(subcommand =>
            subcommand
              .setName('kickgroup')
              .setDescription('Kicks a user from a group')
              .addStringOption(msg =>
                msg
                  .setName('group')
                  .setDescription(`Group to add to`)
                  .setRequired(true),
              )
              .addUserOption(msg =>
                msg
                  .setName('user')
                  .setDescription('User to modify - defaults to self')
                  .setRequired(false),
              ),
          ),
      )
      .addSubcommandGroup(group =>
        group
          .setName('group')
          .setDescription('Perform an action on a group')
          .addSubcommand(subcommand =>
            subcommand
              .setName('set')
              .setDescription("Modify a group's permissions")
              .addStringOption(msg =>
                msg
                  .setName('group')
                  .setDescription('Group to lookup')
                  .setRequired(true),
              )
              .addStringOption(msg =>
                msg
                  .setName('permission')
                  .setRequired(true)
                  .setDescription(
                    'Which permission should we modify on the user',
                  )
                  .setChoices(
                    ...Object.keys(defaultUserPermissions).map(v => ({
                      name: v,
                      value: v,
                    })),
                  ),
              )
              .addStringOption(msg =>
                msg
                  .setName('value')
                  .setRequired(true)
                  .setDescription('Which value this permission should have')
                  .setChoices(
                    {
                      name: 'true',
                      value: 'true',
                    },
                    {
                      name: 'false',
                      value: 'false',
                    },
                  ),
              ),
          )
          .addSubcommand(subcommand =>
            subcommand
              .setName('get')
              .setDescription("Get a group's permissions")
              .addStringOption(msg =>
                msg
                  .setName('group')
                  .setDescription('Group to lookup')
                  .setRequired(true),
              ),
          )
          .addSubcommand(subcommand =>
            subcommand
              .setName('create')
              .setDescription('Creates a group')
              .addStringOption(msg =>
                msg
                  .setName('group')
                  .setDescription('Group Name')
                  .setRequired(true),
              ),
          )
          .addSubcommand(subcommand =>
            subcommand
              .setName('remove')
              .setDescription('Removes a group')
              .addStringOption(msg =>
                msg
                  .setName('group')
                  .setDescription('Group Name')
                  .setRequired(true),
              ),
          ),
      ),
  )
  .requireBotPermission('managePermissions')
  .register() // this builds, registers and returns a Command - not a CommandBuilder!
  .on('run', async e => {
    const handleAddRemoveGroup = async (action: 'add' | 'remove') => {
      const group = e.options.getString('group', true);
      const invokerUserEntry = (await getUserEntry(e.user, true))!;
      const invokerEntry = await getUserPermissionsFromEntry(invokerUserEntry)!;
      if (!invokerEntry)
        return e.reply({
          content: 'Could not find your permission entry',
          ephemeral: true,
        });
      if (!invokerEntry.developer && !invokerUserEntry.groups.includes(group))
        return e.reply({
          content:
            "You aren't even in that group! (you need to be in it unless you are a developer to modify it)",
          ephemeral: true,
        });
      const target = e.options.getUser('user', false) ?? e.user;
      const entry = await getUserEntry(target, true);
      if (!entry)
        return e.reply({
          content: 'Could not find user',
          ephemeral: true,
        });
      if (action === 'add' ? !(await getGroups([group]))[0] : false)
        return e.reply({
          content: 'Could not find group',
          ephemeral: true,
        });
      if (action === 'add') entry.groups.push(group);
      else entry.groups = entry.groups.filter(v => v !== group);
      await entry.save();
      await e.reply({
        embeds: [
          new Embed()
            .setTitle(`Updated Member ${target.username}`)
            .setDescription(
              `${action === 'add' ? 'Added' : 'Removed'} ${target.username} ${
                action === 'add' ? 'to' : 'from'
              } ${group}`,
            ),
        ],
        ephemeral: true,
      });
    };
    switch (e.options.getSubcommandGroup(true)) {
      case 'user':
        switch (e.options.getSubcommand(true)) {
          case 'get': {
            const forceCreate =
              e.options.getBoolean('forcecreate', false) ?? false;
            const entry = await getUserEntry(
              e.options.getUser('user', false) ?? e.user,
              forceCreate,
            );
            if (!entry)
              return e.reply({
                content: 'Could not find user',
                ephemeral: true,
              });
            if (forceCreate) await entry.save();
            await e.reply({
              embeds: [
                new Embed().setTitle(`User ${entry?.userId}`).addFields(
                  {
                    name: 'Permissions',
                    value:
                      Object.entries(entry.permissions ?? {})
                        .map(([a, b]) => `- \`${a}\`: ${b ? check : cross}`)
                        .join('\n') +
                      `
*These only include permissions ${
                        entry.userName ?? entry.userId ?? 'the user'
                      } directly has, not permissions inherited from groups.*`,
                  },
                  {
                    name: 'Groups',
                    value: entry?.groups?.map(v => `- ${v}`)?.join('\n'),
                  },
                ),
              ],
            });
            break;
          }

          case 'set': {
            const permChanged = e.options.getString(
              'permission',
              true,
            ) as UserPermission;
            const permValueRaw = e.options.getString('value', true);
            const permValue = permValueRaw === 'true';
            const invokerEntry = await getUserPermissions(e.user, true)!;
            const entry = await getUserEntry(
              e.options.getUser('user', false) ?? e.user,
              true,
            )!;
            if (!invokerEntry)
              return e.reply({
                content: 'Could not find your permission entry',
                ephemeral: true,
              });
            if (!entry)
              return e.reply({
                content: 'Could not find entry',
                ephemeral: true,
              });
            if (!invokerEntry[permChanged] && !invokerEntry.developer)
              return e.reply({
                content: "You don't even have that permission yourself!",
                ephemeral: true,
              });
            entry.permissions = entry.permissions ?? defaultUserPermissions;
            entry.permissions[permChanged] = permValue;
            await entry.save();
            await e.reply({
              embeds: [
                new Embed()
                  .setTitle(`Updated User ${entry?.userId}`)
                  .setDescription(
                    `Set \`${permChanged}\` to \`${permValue}\` for <@${
                      entry?.userId
                    }>${
                      permValue === false
                        ? `
They may still have the permission if one of their groups has it.`
                        : ''
                    }`,
                  ),
              ],
              ephemeral: true,
            });
            break;
          }

          case 'addgroup':
            await handleAddRemoveGroup('add');
            break;

          case 'kickgroup':
            await handleAddRemoveGroup('remove');
            break;

          default:
            break;
        }
        break;
      case 'group': {
        switch (e.options.getSubcommand(true)) {
          case 'get': {
            const group = e.options.getString('group') ?? 'no-group-specified';
            const entry = (await getGroups([group]))[0];
            if (!entry)
              return e.reply({
                content: 'Could not find entry',
                ephemeral: true,
              });
            await e.reply({
              embeds: [
                new Embed().setTitle(`Group ${group}`).addFields({
                  name: 'Permissions',
                  value: Object.entries(entry.permissions ?? {})
                    .map(([a, b]) => `- \`${a}\`: ${b ? check : cross}`)
                    .join('\n'),
                }),
              ],
            });
          }

          case 'set': {
            const permChanged = e.options.getString(
              'permission',
              true,
            ) as UserPermission;
            const permValueRaw = e.options.getString('value', true);
            const permValue = permValueRaw === 'true';
            const invokerEntry = await getUserPermissions(e.user, true)!;
            const entry = (
              await getGroups([
                e.options.getString('group', false) ?? 'no-group-specified',
              ])
            )[0];
            if (!invokerEntry)
              return e.reply({
                content: 'Could not find your permission entry',
                ephemeral: true,
              });
            if (!entry)
              return e.reply({
                content:
                  'Could not find group. Does it exist?\nFYI, Users can be in nonexistent groups',
                ephemeral: true,
              });
            if (!invokerEntry[permChanged] && !invokerEntry.developer)
              return e.reply({
                content: "You don't even have that permission yourself!",
                ephemeral: true,
              });
            entry.permissions = entry.permissions ?? defaultUserPermissions;
            entry.permissions[permChanged] = permValue;
            await entry.save();
            await e.reply({
              embeds: [
                new Embed()
                  .setTitle(`Updated Group ${entry.groupName}`)
                  .setDescription(
                    `Set \`${permChanged}\` to \`${permValue}\` for ${entry?.groupName}`,
                  ),
              ],
              ephemeral: true,
            });
            break;
          }

          case 'create': {
            const invokerEntry = await getUserPermissions(e.user, true)!;
            if (!invokerEntry.developer)
              return await e.reply({
                embeds: [
                  new Embed()
                    .error(
                      "You don't have permission to do this. You need the developer permission to create or remove groups.",
                    )
                    .setFooter({
                      text: 'ERR_NO_PERM',
                    }),
                ],
                ephemeral: true,
              });
            const grpN = e.options.getString('group', true)!;
            if ((await getGroups([grpN])).filter(v => v !== null).length !== 0)
              return await e.reply({
                embeds: [
                  new Embed()
                    .error('A group with this name already exists.')
                    .setFooter({
                      text: 'ERR_GRP_EXISTS',
                    }),
                ],
                ephemeral: true,
              });
            const group = new PermissionGroupModel();
            group.groupName = grpN;
            await group.save();
            return await e.reply({
              embeds: [
                new Embed()
                  .setTitle('Success!')
                  .setDescription(`Created Group \`${grpN}\`!`),
              ],
              ephemeral: true,
            });
          }

          case 'remove': {
            const invokerEntry = await getUserPermissions(e.user, true)!;
            if (!invokerEntry.developer)
              return await e.reply({
                embeds: [
                  new Embed()
                    .error(
                      "You don't have permission to do this. You need the developer permission to create or remove groups.",
                    )
                    .setFooter({
                      text: 'ERR_NO_PERM',
                    }),
                ],
                ephemeral: true,
              });
            const grpN = e.options.getString('group', true)!;
            if ((await getGroups([grpN])).filter(v => v !== null).length === 0)
              return await e.reply({
                embeds: [
                  new Embed()
                    .error('No group exist with this name.')
                    .setFooter({
                      text: 'ERR_GRP_NO_EXIST',
                    }),
                ],
                ephemeral: true,
              });
            await PermissionGroupModel.findOneAndDelete({
              groupName: grpN,
            });
            return await e.reply({
              embeds: [
                new Embed()
                  .setTitle('Success!')
                  .setDescription(`Deleted Group \`${grpN}\`!`),
              ],
              ephemeral: true,
            });
          }

          default:
            break;
        }
      }

      default:
        break;
    }
  });
export default PermCommand;
